package com.nulldev.util.networking.tcp_legacy.client;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.regex.Pattern;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.data.Variables;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.networking.tcp_legacy.TCPCore;
import com.nulldev.util.networking.tcp_legacy.fRetry;

@Deprecated
public abstract class TCPClient extends fRetry {
	public volatile boolean PREFER_THREAD_API;
	public final Logger logger = LoggerFactory.getLoggerD(true);
	private volatile boolean connected;
	private volatile String host;
	private volatile int port;

	private volatile ClientConnection connection;
//	private volatile int reconnectDelay = 10 * 1000;
	private final Object waitForConnection = new Object();
	final Object waitForClose = new Object();

	public TCPClient(String category, String name, String host, int port) {
		super(category, name);
		this.host = host;
		this.port = port;
	}

	public TCPClient(String category, String name) {
		super(category, name);
		this.host = "";
		this.port = 0;
	}

	public void updateHost(final String host, final int port) {
		this.host = host;
		this.port = port;
	}

	public boolean isConnected() {
		return this.connected;
	}

	public Logger getLogger() {
		return this.logger;
	}

//	private volatile long tries = 0;
	private volatile Socket socket;

	protected void retry() {
		socket = null;
		if (host == "" && port == 0) {
			return;
		}
		try {
			socket = new Socket(host, port);
		} catch (IOException ex) {
			if (logger.isErrorEnabled())
				logger.error(category, "Unable to connect: " + host + ":" + port);
			failed();
			return;
		}
		success();
		try {
			socket.setKeepAlive(true);
			// socket.setReceiveBufferSize(100000000);
			// socket.setSendBufferSize(100000000);
		} catch (SocketException e) {
			JVM.errorln(e);
		}
		if (logger.traceEnabled.get())
			logger.trace(category, "Connected: " + socket.getInetAddress() + ":" + socket.getPort());

		try {
			connection = new ClientConnection(category, name, socket);
			connection.start();
			this.connected = true;
		} catch (IOException ex) {
			if (logger.isErrorEnabled())
				logger.error(category, "Error configuring client connection.", ex);
			failed();
			return;
		}

		connected(connection);
		synchronized (waitForConnection) {
			waitForConnection.notifyAll();
		}
		waitForClose(0);
		disconnected(connection);
	}

	protected void stopped() {
		ClientConnection connection = this.connection;
		if (connection != null)
			connection.close();
	}

	public boolean send(String message) {
		ClientConnection connection = this.connection;
		if (connection == null) {
			if (logger.debugEnabled.get())
				logger.debug(category, "Unable to send, connection is closed: " + message);
			return false;
		}
		connection.send(message);
		return true;
	}

	public boolean sendBlocking(String message) {
		message = message.replaceAll(Pattern.quote(" "), "%space");
		ClientConnection connection = this.connection;
		if (connection == null) {
			if (logger.debugEnabled.get())
				logger.debug(category, "Unable to send, connection is closed: " + message);
			return false;
		}
		return connection.sendBlocking(message);
	}

	public boolean sendBlocking(String message, byte[] bytes) {
		return sendBlocking(message, bytes, 0, bytes.length);
	}

	public boolean sendBlocking(String message, byte[] bytes, int offset, int count) {
		ClientConnection connection = this.connection;
		if (connection == null) {
			if (logger.debugEnabled.get())
				logger.debug(category, "Unable to send, connection is closed: " + message);
			return false;
		}
		return connection.sendBlocking(message, bytes, offset, count);
	}

	public void connected(TCPCore connection) {
	}

	public void disconnected(TCPCore connection) {
	}

	public abstract void receive(String event, String payload, byte[] bytes, long count, final long readTime);

	public void onRemove(final String reason) {
	}

	public ClientConnection getConnection() {
		return connection;
	}

	public String format() {
		if (Variables.notNull(this.socket)) {
			return this.socket.getInetAddress().getHostAddress() + ":" + this.socket.getPort();
		} else {
			return this.host + ":" + this.port;
		}
	}

	public int getPort() {
		return this.port;
	}

	public String getIP() {
		return this.socket.getInetAddress().getHostAddress();
	}

	public String formatS() {
		return this.socket.getInetAddress().getHostAddress() + ":" + this.socket.getPort();
	}

	public String formatHS() {
		return this.host + ":" + this.port;
	}

	/** @param millis 0 to wait forever. */
	public boolean waitForConnection(long millis) {
		if (logger.traceEnabled.get())
			logger.trace(category, "Waiting for connection.");
		long until = System.currentTimeMillis() + millis;
		while (true) {
			if (System.currentTimeMillis() >= until) {
				this.updateHost("", 0);
				return false;
			}
			synchronized (waitForConnection) {
				ClientConnection connection = TCPClient.this.connection;
				if (connection != null && !connection.closed)
					return true;
				long wait = 0;
				if (millis > 0) {
					wait = until - System.currentTimeMillis();
					if (wait < 0)
						return false;
				}
				try {
					waitForConnection.wait(wait);
				} catch (InterruptedException ignored) {
				}
			}
		}
	}

	/**
	 * @param millis 0 to wait forever.
	 * @return true if the close happened.
	 */
	public boolean waitForClose(long millis) {
		if (logger.traceEnabled.get())
			logger.trace(category, "Waiting for close.");
		long until = System.currentTimeMillis() + millis;
		while (true) {
			synchronized (waitForClose) {
				ClientConnection connection = TCPClient.this.connection;
				if (connection == null || connection.closed)
					return true;
				long wait = 0;
				if (millis > 0) {
					wait = until - System.currentTimeMillis();
					if (wait < 0)
						return false;
				}
				try {
					waitForClose.wait(wait);
				} catch (InterruptedException ignored) {
				}
			}
			if (logger.traceEnabled.get())
				logger.trace(category, "Waiting for close...");
		}
	}

	public class ClientConnection extends TCPCore {
		private final Socket socket;

		public ClientConnection(String category, String name, Socket socket) throws IOException {
			super(category, name, socket);
			this.PREFER_THREAD_API = TCPClient.this.PREFER_THREAD_API;
			this.socket = socket;
		}

		public void receive(String event, String payload, byte[] bytes, long count, final long readTime) {
			TCPClient.this.receive(event, payload, bytes, count, readTime);
		}

		public boolean close() {
			super.close();
			synchronized (waitForClose) {
				waitForClose.notifyAll();
				return true;
			}
		}

		public String getIP() {
			return TCPClient.this.getIP();
		}

		public int getPort() {
			return TCPClient.this.getPort();
		}

		public TCPClient getTCPClient() {
			return TCPClient.this;
		}

		public void callDisconnect() {
			TCPClient.this.requestDisconnect();
		}

		public void onRemoved(final String reason) {
			TCPClient.this.onRemove(reason);
		}

		private byte[] publicKey;

		public void setPublicKey(final byte[] keyData) {
			if (keyData != null) {
				this.publicKey = keyData;
			}
		}

		public boolean hasPublicKey() {
			return this.publicKey != null;
		}

		public byte[] getPublicKey() {
			return this.publicKey;
		}

		public Socket socket() {
			return this.socket;
		}
	}

	public void requestDisconnect() {
		this.updateHost("", 0);
		this.connection.requestDisconnect();
		this.connected = false;
	}

	public String createString(final byte[] data) {
		return new String(data);
	}

	public Socket socket() {
		return this.socket;
	}
}
